import createDOMElement from "./createDOMElement"
import diffComponent from "./diffComponent"
import mountElement from "./mountElement"
import unmountNode from "./unmountNode"
import updateNodeElement from "./updateNodeElement"
import updateTextNode from "./updateTextNode"
export default function diff(virtualDOM,container,oldDOM) {
    const oldVirtualDOM = oldDOM && oldDOM._virtualDOM
    const oldComponent = oldVirtualDOM && oldVirtualDOM.component
    // 判断旧DOM是否存在
        // 不存在直接创建挂载
        // 存在进行更新或替换删除操作
    if(!oldDOM){
        mountElement(virtualDOM,container)
    } else if(typeof virtualDOM.type !=='function' && virtualDOM.type !== oldVirtualDOM.type){ 
        // 类型不相同时  且不为组件时  组件需要单独进行处理
        const newElement = createDOMElement(virtualDOM)
        oldDOM.parentNode.replaceChild(newElement,oldDOM)
    } else if(typeof virtualDOM.type ==='function'){ 
        // 渲染的是一个组件
        // virtualDOM组件本身的virtualDOM  新render的组件
        // oldComponent 要更新的组件的实例对象 通过这个实例对象可以调用组件的生命周期函数 可以将最新的props更新到组件的实例对象上 同理也可以调用此对象上的render获取最新的VirtualDOM
        // oldDOM 用于获取旧的DOM和旧的VDOM进行Diff对比
        // container 不是同一个组件时  直接挂载就可以
        diffComponent(virtualDOM,oldComponent,oldDOM,container)
    } else if(oldVirtualDOM && virtualDOM.type === oldVirtualDOM.type){
        // 如果oldVirtualDOM存在 即证明不是首次渲染 有更新的内容存在  
        if(virtualDOM.type === 'text'){
            // 更新的是普通的Text文本节点
            updateTextNode(virtualDOM,oldVirtualDOM,oldDOM)
        } else {
            // 更新的是普通的元素节点
            updateNodeElement(oldDOM,virtualDOM,oldVirtualDOM)
        }

        
        // 更新DOM元素(根据key)只需要关注元素是否需要重新渲染+元素的位置是否正确
        // 1、循环旧元素DOM节点 判断是否有key属性 如果有 就进行缓存
        let keyedElements = {}
        for (let i = 0,len = oldDOM.childNodes.length; i < len; i++) {
            const domElement = oldDOM.childNodes[i];
            if(domElement.nodeType === 1) {
                // 是元素节点
                let key = domElement.getAttribute("key")
                key && (keyedElements[key] = domElement)
            }
        }

        let hasNoKey = Object.keys(keyedElements).length === 0
        if(hasNoKey){
            // 对比子节点
            virtualDOM.children.forEach((child,index) => {
                diff(child,oldDOM,oldDOM.childNodes[index])
            });
        } else {
            // 2、循环 virtualDOM 节点 根据步骤一中的缓存key进行判断是否有key属性 如果有 就进行缓存
            virtualDOM.children.forEach((child,i) => {
                // virtualDOM中的key存储在props上
                let key = child.props.key
                if(key) {
                    let domElement = keyedElements[key]
                    if(domElement){
                        // 3、判断当前位置的元素是不是我们所期望的元素
                        if(oldDOM.childNodes[i] && oldDOM.childNodes[i] !== domElement){
                            // 不是期望的元素则进行位置替换
                            oldDOM.insertBefore(domElement,oldDOM.childNodes[1])
                        }
                    } else {
                        // 没有找到则表示是新增元素
                        mountElement(child,oldDOM,oldDOM.childNodes[i])
                    }
                }
            })
        }
        
        /**
         * @aid 删除节点
         * @desc 删除节点发生在节点更新之后、删除节点发生在同一个父节点下的所有子节点上(有范围限制)
         * @logic 新节点的数量和旧节点的数量进行对比  当旧节点子节点数量大于新节点的子节点数量时进行倒序删除即可(此时新节点已经对比更新完成，因此可以直接进行倒序数量对比删除即可 )
         */
        // 获取旧节点
        let oldChildNodes = oldDOM.childNodes

        // 判断新旧节点的数量是否相同  旧节点数量大于新节点时就需要进行删除操作了 删除操作是倒序进行对比删除的
        if(oldChildNodes.length > virtualDOM.children.length){
            if(hasNoKey){
                // 有节点被删除了
                for (let i = oldChildNodes.length-1; i > virtualDOM.children.length-1; i--) {
                    unmountNode(oldChildNodes[i])
                }
            } else {
                // 通过key属性删除节点
                // 拿旧节点的可以去新节点中进行对应查找  如果找不到就说明被删除了
                for (let i = 0; i < oldChildNodes.length; i++) {
                    const oldChild = oldChildNodes[i];
                    const oldChildKey = oldChild._virtualDOM.props.key
                    let found = false
                    for (let n = 0; n < virtualDOM.children.length; n++) {
                        if(oldChildKey === virtualDOM.children[n].props.key){
                            found = true
                            break
                        }
                    }
                    if(!found){
                        unmountNode(oldChild)
                    }
                    
                }
            }
            
        }
    }
}